library(tidyverse)
── Attaching core tidyverse packages ──────────── tidyverse 2.0.0 ──
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ lubridate 1.9.2     ✔ tibble    3.2.1
✔ purrr     1.0.1     ✔ tidyr     1.3.0
✔ readr     2.1.4     ── Conflicts ────────────────────────────── tidyverse_conflicts() ──
✖ tidyr::expand() masks Matrix::expand()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
✖ tidyr::pack()   masks Matrix::pack()
✖ tidyr::unpack() masks Matrix::unpack()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors

1 load & explore data

Load the housing_prices.csv data set and undertake an initial exploration of the data. You will find details on the data set on the relevant Kaggle page

housing_prices <- read_csv("data/housing_prices.csv")
Rows: 19675 Columns: 10── Column specification ───────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): ocean_proximity
dbl (9): longitude, latitude, housing_median_age, total_rooms, total_bedrooms, population, households, median_i...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
glimpse(housing_prices)
Rows: 19,675
Columns: 10
$ longitude          <dbl> -122.23, -122.22, -122.24, -122.25, -122.25, -122.25, -122.25, -122.25, -122.26, -122.…
$ latitude           <dbl> 37.88, 37.86, 37.85, 37.85, 37.85, 37.85, 37.84, 37.84, 37.84, 37.84, 37.85, 37.85, 37…
$ housing_median_age <dbl> 41, 21, 52, 52, 52, 52, 52, 52, 42, 52, 52, 52, 52, 52, 52, 50, 52, 52, 50, 52, 40, 42…
$ total_rooms        <dbl> 880, 7099, 1467, 1274, 1627, 919, 2535, 3104, 2555, 3549, 2202, 3503, 2491, 696, 2643,…
$ total_bedrooms     <dbl> 129, 1106, 190, 235, 280, 213, 489, 687, 665, 707, 434, 752, 474, 191, 626, 283, 347, …
$ population         <dbl> 322, 2401, 496, 558, 565, 413, 1094, 1157, 1206, 1551, 910, 1504, 1098, 345, 1212, 697…
$ households         <dbl> 126, 1138, 177, 219, 259, 193, 514, 647, 595, 714, 402, 734, 468, 174, 620, 264, 331, …
$ median_income      <dbl> 8.3252, 8.3014, 7.2574, 5.6431, 3.8462, 4.0368, 3.6591, 3.1200, 2.0804, 3.6912, 3.2031…
$ median_house_value <dbl> 452600, 358500, 352100, 341300, 342200, 269700, 299200, 241400, 226700, 261100, 281500…
$ ocean_proximity    <chr> "NEAR BAY", "NEAR BAY", "NEAR BAY", "NEAR BAY", "NEAR BAY", "NEAR BAY", "NEAR BAY", "N…

Dataset about areas - population, households, features of these. One character column (ocean_proximity), rest are numeric: location, local population size and number of households, number of rooms and bedrooms in the population’s households, median age (of the population from 1990 census), median income, median house value.

housing_prices %>% 
  summarise(across(everything(),
                   ~sum(is.na(.x))))

Only var with missing values is total_bedrooms, 200 NAs out of the 19,675 observations. Tbc how to deal with these, if at all.

2 check for explanatory vars that correlate

We expect the total_rooms of houses to be strongly correlated with total_bedrooms. Use ggpairs() to investigate correlations between these two variables.

It’s overkill to look at all the vars, so just look at the two of interest:

housing_prices %>% 
  select(total_rooms, total_bedrooms) %>% 
  GGally::ggpairs()

They look strongly positively correlated, and corr = 0.934 so that indicates a very strong positive correlation. (And *** indicate this is statistically significant, i.e. we can reject the null hypothesis that total_bedrooms does not affect total_rooms; it is very unlikely this data is from a world where the null hypothesis is true.)

3 trim data

drop total_bedrooms from the dataset, and use only total_rooms going forward

Because these variables are related, we should only use one of these in our model.

housing_prices <- housing_prices %>% 
  select(-total_bedrooms)

total_bedrooms was the only var with missing values, so we also don’t need to worry about these anymore.

4 explore numeric predictors

We are interested in developing a regression model for the median_house_value of a house in terms of the possible predictor variables in the dataset. i. Use ggpairs() to investigate correlations between median_house_value and the predictors (this may take a while to run, don’t worry, make coffee or something).

GGally::ggpairs(housing_prices)

From these, it looks like there are otherexplanatory variables that are strongly related:

Taking these into account, and looking at the magnitude of the correlation coefficients that are significant (*s), I would consider including the following in a model, in order of steps:

  1. income
  2. location (lat, long) - look at lat below
  3. demand (something derived from pop, household, rooms) - look at rooms below
  1. age
  1. Perform further ggplot visualisations of any significant correlations you find.
# income
housing_prices %>% 
  ggplot(aes(x = median_income, y = median_house_value)) +
  geom_point(fill = "grey60", size = 0.5) +
  geom_smooth(method = lm)

cor(housing_prices$median_house_value, housing_prices$median_income)
[1] 0.6426108

Interpret: There is a strong positive correlation between median house value and median income of an area. 64% of variation in house value is explained by variation in income.

This could be a useful explanatory variable.

Note also there seems to be an upper limit in median_house_value, odd lack of values above 500,000.

housing_prices %>% 
  ggplot(aes(x = latitude, y = median_house_value)) +
  geom_point(fill = "grey60") +
  geom_smooth(method = lm)

This is not a linear relationship - ther appear to be clusters where house price is more variable (higher peaks) - i.e. certain latitudes, I suspect these are where cities are.

Do not use this as a variable on its own (consider using location though - maybe even encoding as different areas?)

# total_rooms
housing_prices %>% 
  ggplot(aes(x = total_rooms, y = median_house_value)) +
  geom_point(fill = "grey60") +
  geom_smooth(method = lm)

This does not look homoeskedastic, lots of data points at lower total_rooms, but with high degree of spread of house values. Suggest total_rooms might need to be standardised by some factor?

# median_age
housing_prices %>% 
  ggplot(aes(x = housing_median_age, y = median_house_value)) +
  geom_point(fill = "grey60") +
  geom_smooth(method = lm)

No obvious correlation here. Do not use in model.

5 explore categorical predictor

Shortly we may try a regression model to fit the categorical predictor ocean_proximity. Investigate the level of ocean_proximity predictors. How many dummy variables do you expect to get from it?

housing_prices %>% 
  distinct(ocean_proximity)

There are 5 distinct categories in ocean_proximity, so expect to generate 4 dummy variables. Set inland as the reference, everything else gets more proximal to ocean.

housing_prices_trim <- housing_prices %>% 
  # set up dummy variables for ocean_proximity
  fastDummies::dummy_cols(select_columns = "ocean_proximity",
                          remove_selected_columns = TRUE) %>% 
  janitor::clean_names() %>% 
  # set inland as reference
  select(-ocean_proximity_inland)
# check for any potential correlations
housing_prices %>% 
  ggplot(aes(x = ocean_proximity, y = median_house_value)) +
  geom_boxplot()

House prices on island look higher than the rest, this may be a sensible predictor to use in a model.

Update: from boxplot, actually looks like 3 categories that are similar: inland, island, and proximal to ocean (combining the 3 others) –> which would suggest 2 dummy variables (with inland set as reference).

housing_prices %>% 
  summarise(count = n(), .by = ocean_proximity)

~20,000 observations of which, 8,600 are <1h to ocean, 5 are on island, 2000 are near bay, 2446 are near ocean, rest are inland (~6,500).

There are only 5 observations for island, so not enough data here to model this category, and also dragging up house prices - suggest dropping this category altogether for now.

So let’s use inland as reference, drop island observations, and combine the rest into a predictor “ocean_proximal”:

housing_prices_ocean <- housing_prices %>% 
  filter(ocean_proximity != "ISLAND") %>% 
  mutate(ocean_proximal = if_else(
    ocean_proximity == "INLAND",
    FALSE, TRUE)) %>% 
  select(-ocean_proximity)

head(housing_prices_ocean)

homework review

In R, we don’t need to manually make the dummy variables - just keep the ocean_proximity column and pass this into the model below. Similar results, see the coefficient estimates for each type of ocean_proximity.

Example:

# iris dataset, 1 continuous, 1 categorical var
lm1 <- lm(Petal.Length ~ Petal.Width + Species, iris)

head(model.matrix(lm1))
  (Intercept) Petal.Width Speciesversicolor Speciesvirginica
1           1         0.2                 0                0
2           1         0.2                 0                0
3           1         0.2                 0                0
4           1         0.2                 0                0
5           1         0.2                 0                0
6           1         0.4                 0                0
# shows that the Species var are dummy encoding automatically:

Also: it’s ok to keep the island in with 5 observations; the difference in sample size is not a problem. It might mean we are less empowered to make predictions about island house values later, but it’s ok to include in the model. Also ok to combine similar groups.

If you include n dummy variables, you’ll get one of them with a line of NAs in summary(model) <- a good sign of multicolinearity and you need to remove 1 dummy var.

6 simple linear regression with income

Start with simple linear regression. Regress median_house_value on median_income and check the regression diagnostics.

model_income <- lm(formula = median_house_value ~ median_income, housing_prices_trim)

library(ggfortify)
autoplot(model_income)

Intrepret:

# inspect the high leverage / influencing points
housing_prices_trim %>% 
  ggplot(aes(x = median_income, y = median_house_value)) +
  geom_point(fill = "grey60", size = 0.5) +
  geom_smooth(method = lm) +
  geom_text(aes(label = 1:nrow(housing_prices)), nudge_x = 0.5, nudge_y = 1, size = 2)

Points 11331, 17556 and 1555 appear to be the points of interest.

housing_prices[c(11331,17556,1555),]

Raw data doesn’t look suspect, not an obvious processing error. But note the population looks tiny, very few households. Might want to consider filtering for data where households is sizeable enough…

housing_prices %>% 
  ggplot(aes(x = population)) +
  geom_histogram(bins = 100, colour = "white")


housing_prices %>% 
  ggplot(aes(x = households)) +
  geom_histogram(bins = 100, colour = "white")

It doesn’t make sense to filter out low number households or population, given the distributions here - they don’t seem to be odd.

Try removing a couple of points:

Removing these values has not helped the model, it just shows even more extreme values (high income, low house price). So do not remove these points (continue with housing_prices_trim).

summary(model_income)

Call:
lm(formula = median_house_value ~ median_income, data = housing_prices_trim)

Residuals:
    Min      1Q  Median      3Q     Max 
-513966  -51564  -13979   36549  403935 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)    45457.0     1359.0   33.45   <2e-16 ***
median_income  39987.0      339.9  117.64   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 74870 on 19673 degrees of freedom
Multiple R-squared:  0.4129,    Adjusted R-squared:  0.4129 
F-statistic: 1.384e+04 on 1 and 19673 DF,  p-value: < 2.2e-16

Here, the model suggests a function:

median_house_value (in $) ~= ~40,000*median_income + 45,500

the p-value (<2.2e-16) is very small (much below a 0.05 significance threshold), which indicates it is highly unlikely this non-zero coefficient would happen in a world where the null hypothesis is true (that median_income has no affect on median_house_value), so we can reject the null hypothesis and say there is enough evidence to suggest that income does affect house value.

The standard error is sizeable (~USD 75,000) - we are talking about house prices in the region of USD 300-500,000 with a an IQR of ~ 130 (the QQ range is ~ 120-250k in the boxplot below) so this error is half the IQR so is quite large for this to be a good model.

housing_prices %>% 
  ggplot(aes(x = median_house_value)) +
  geom_boxplot()

The R2 is 0.4129, which is an ok correlation for such a variable outcome (house value).

Overall this simple linear model does not meet several assumptions of linear regression, so we should not accept that this model is a good fit for our data. We need to improve it - we can try adding another predictor variable in…

7 add another var to model

Add another predictor of your choice. Check your assumptions, diagnostics, and interpret the model.

7a + island

Let’s try with ocean proximity: island.

Considering it as an independent variable, but not using the other ocean_proximities – not sure if this is a correct approach for non-binary categorical variables…

Oh this has not improved the model…

  • RvF - still got a wedge shape, so not linear
  • QQ - residuals still not normally distributed
  • S-L still got a wedge here too, so heterskedastic
  • RvL - shows many points are near the centroid, a couple of points are very far

This distribution doesn’t look so bad though - maybe it is shifted left?

summary(model_income_island)

Call:
lm(formula = median_house_value ~ median_income + ocean_proximity_island, 
    data = housing_prices_trim)

Residuals:
    Min      1Q  Median      3Q     Max 
-514154  -51494  -13940   36548  404045 

Coefficients:
                       Estimate Std. Error t value Pr(>|t|)    
(Intercept)             45320.1     1357.6  33.382  < 2e-16 ***
median_income           40008.7      339.6 117.828  < 2e-16 ***
ocean_proximity_island 225319.3    33449.9   6.736 1.67e-11 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 74780 on 19672 degrees of freedom
Multiple R-squared:  0.4143,    Adjusted R-squared:  0.4142 
F-statistic:  6958 on 2 and 19672 DF,  p-value: < 2.2e-16
mosaic::plotModel(model_income_island)

I would expect to have seen parallel slopes here (one for island = TRUE, another for island = FALSE) - but there is only one line.

housing_prices_trim %>% 
  filter(ocean_proximity_island == 1)

There are only 5 observations with island == TRUE so cannot use this in our model!

Try again…

7b + house_size / occupancy

Let’s derive mean house_size using total_rooms/households

housing_prices_trim <- housing_prices_trim %>% 
  mutate(mean_house_size = total_rooms / households)

head(housing_prices_trim)
housing_prices_trim %>% 
  ggplot(aes(x = mean_house_size, y = median_house_value)) +
  geom_point(size = 0.5)

This is a no-goer - looks like majority of houses are around the same size, with a few extreme values…

What about demand, some kind of measure of households by population? Derive avg_occupancy

housing_prices_trim <- housing_prices_trim %>% 
  select(-mean_house_size) %>% 
  mutate(avg_occupancy = population / households) # indicates # people per household

head(housing_prices_trim)
housing_prices_trim %>% 
  ggplot(aes(x = avg_occupancy, y = median_house_value)) +
  geom_point(size = 0.5)

Now there are 7 points that look like extreme values… could we filter for avg_occupancy < 100??

This may be over-wrangling the data, but these few values do not make sense for a model looking at ~20,000 observations

nrow(housing_prices_trim)
[1] 19675
# > 100 filters 4 out
# > 50 filters 7 out
housing_prices_rm_high_occupancy <- housing_prices_trim %>% 
  filter(!avg_occupancy > 50)
housing_prices_rm_high_occupancy %>% 
  ggplot(aes(x = avg_occupancy, y = median_house_value)) +
  geom_point(size = 0.5)

Now we can see scatter of data, but still does not look to have any linear relationship. Could try scaling but I don’t think that is the problem here…

7c + ocean_proximity

Using house_prices_ocean for our dummy variable ocean_proximal:

housing_prices_ocean %>% 
  ggplot(aes(x = ocean_proximal, y = median_house_value)) +
  geom_boxplot()

There looks to be an effect of ocean proximity with house prices, where being close to the ocean has higher house prices, but there’s also a long tail of values for FALSE. Let’s try including this in our model as a second explanatory variable:

model_income_ocean <- lm(median_house_value ~ median_income + ocean_proximal, 
                         housing_prices_ocean)

This gives us 2 parallel lines - it looks as though being close to the ocean shifts median house_value higher.

Let’s diagnose this model:

  1. RvF - still a wedge shape, so not linear
  2. QQ plot does not suggest normally distributed at the extremes
  3. S-L - still a wedge shape so heterskedastic
  4. there may still be a couple of points that are influencing the model here

So the model does not match our assumptions of linear regression.

Let’s look at the model anyway…

summary(model_income_ocean)

Call:
lm(formula = median_house_value ~ median_income + ocean_proximal, 
    data = housing_prices_ocean)

Residuals:
    Min      1Q  Median      3Q     Max 
-482054  -42181  -11818   27998  376321 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)         12000.4     1268.3   9.462   <2e-16 ***
median_income       34888.2      305.4 114.237   <2e-16 ***
ocean_proximalTRUE  78026.8     1018.6  76.599   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 65630 on 19667 degrees of freedom
Multiple R-squared:  0.5485,    Adjusted R-squared:  0.5485 
F-statistic: 1.195e+04 on 2 and 19667 DF,  p-value: < 2.2e-16

This model (if it were a good fit) suggests base median house value is USD 12,000 and increases with median income (multiplier effect, *35,000), and is also shifted up if the location is close to the ocean (by USD 78,000).

The R2 indicates the model is a good fit, and the p-value is way below a signficance threshold of 0.05, suggesting we can reject the null hypothesis (that these explanatory variables do not predict house value).

This model summary would look good, except that looking at autoplot indicates the linear model is not appropriate here.

To try: standardise or scale the variables somehow? But this won’t change the distribution of each (i.e. won’t shift the long tail of high house prices or high income…)

LS0tCnRpdGxlOiAid2sxMGQyIGhvbWV3b3JrIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoamFuaXRvcikKYGBgCgojIDEgbG9hZCAmIGV4cGxvcmUgZGF0YQoKPiBMb2FkIHRoZSBob3VzaW5nX3ByaWNlcy5jc3YgZGF0YSBzZXQgYW5kIHVuZGVydGFrZSBhbiBpbml0aWFsIGV4cGxvcmF0aW9uIG9mIHRoZSBkYXRhLiBZb3Ugd2lsbCBmaW5kIGRldGFpbHMgb24gdGhlIGRhdGEgc2V0IG9uIHRoZSByZWxldmFudCBbS2FnZ2xlIHBhZ2VdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvY2FtbnVnZW50L2NhbGlmb3JuaWEtaG91c2luZy1wcmljZXMpIAoKYGBge3J9CmhvdXNpbmdfcHJpY2VzIDwtIHJlYWRfY3N2KCJkYXRhL2hvdXNpbmdfcHJpY2VzLmNzdiIpCmBgYAoKYGBge3J9CmdsaW1wc2UoaG91c2luZ19wcmljZXMpCmBgYAoKRGF0YXNldCBhYm91dCBhcmVhcyAtIHBvcHVsYXRpb24sIGhvdXNlaG9sZHMsIGZlYXR1cmVzIG9mIHRoZXNlLiBPbmUgY2hhcmFjdGVyIGNvbHVtbiAob2NlYW5fcHJveGltaXR5KSwgcmVzdCBhcmUgbnVtZXJpYzogbG9jYXRpb24sIGxvY2FsIHBvcHVsYXRpb24gc2l6ZSBhbmQgbnVtYmVyIG9mIGhvdXNlaG9sZHMsIG51bWJlciBvZiByb29tcyBhbmQgYmVkcm9vbXMgaW4gdGhlIHBvcHVsYXRpb24ncyBob3VzZWhvbGRzLCBtZWRpYW4gYWdlIChvZiB0aGUgcG9wdWxhdGlvbiBmcm9tIDE5OTAgY2Vuc3VzKSwgbWVkaWFuIGluY29tZSwgbWVkaWFuIGhvdXNlIHZhbHVlLgoKYGBge3J9CmhvdXNpbmdfcHJpY2VzICU+JSAKICBzdW1tYXJpc2UoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgICAgIH5zdW0oaXMubmEoLngpKSkpCmBgYAoKT25seSB2YXIgd2l0aCBtaXNzaW5nIHZhbHVlcyBpcyB0b3RhbF9iZWRyb29tcywgMjAwIE5BcyBvdXQgb2YgdGhlIDE5LDY3NSBvYnNlcnZhdGlvbnMuIFRiYyBob3cgdG8gZGVhbCB3aXRoIHRoZXNlLCBpZiBhdCBhbGwuCgojIDIgY2hlY2sgZm9yIGV4cGxhbmF0b3J5IHZhcnMgdGhhdCBjb3JyZWxhdGUKCj4gV2UgZXhwZWN0IHRoZSB0b3RhbF9yb29tcyBvZiBob3VzZXMgdG8gYmUgc3Ryb25nbHkgY29ycmVsYXRlZCB3aXRoIHRvdGFsX2JlZHJvb21zLiBVc2UgZ2dwYWlycygpIHRvIGludmVzdGlnYXRlIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHRoZXNlIHR3byB2YXJpYWJsZXMuCgpJdCdzIG92ZXJraWxsIHRvIGxvb2sgYXQgYWxsIHRoZSB2YXJzLCBzbyBqdXN0IGxvb2sgYXQgdGhlIHR3byBvZiBpbnRlcmVzdDoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KaG91c2luZ19wcmljZXMgJT4lIAogIHNlbGVjdCh0b3RhbF9yb29tcywgdG90YWxfYmVkcm9vbXMpICU+JSAKICBHR2FsbHk6OmdncGFpcnMoKQpgYGAKClRoZXkgbG9vayBzdHJvbmdseSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQsIGFuZCBjb3JyID0gMC45MzQgc28gdGhhdCBpbmRpY2F0ZXMgYSB2ZXJ5IHN0cm9uZyBwb3NpdGl2ZSBjb3JyZWxhdGlvbi4gKEFuZCAqKiogaW5kaWNhdGUgdGhpcyBpcyBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LCBpLmUuIHdlIGNhbiByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyB0aGF0IHRvdGFsX2JlZHJvb21zIGRvZXMgbm90IGFmZmVjdCB0b3RhbF9yb29tczsgaXQgaXMgdmVyeSB1bmxpa2VseSB0aGlzIGRhdGEgaXMgZnJvbSBhIHdvcmxkIHdoZXJlIHRoZSBudWxsIGh5cG90aGVzaXMgaXMgdHJ1ZS4pCgojIDMgdHJpbSBkYXRhCgo+IGRyb3AgdG90YWxfYmVkcm9vbXMgZnJvbSB0aGUgZGF0YXNldCwgYW5kIHVzZSBvbmx5IHRvdGFsX3Jvb21zIGdvaW5nIGZvcndhcmQKCkJlY2F1c2UgdGhlc2UgdmFyaWFibGVzIGFyZSByZWxhdGVkLCB3ZSBzaG91bGQgb25seSB1c2Ugb25lIG9mIHRoZXNlIGluIG91ciBtb2RlbC4KCmBgYHtyfQpob3VzaW5nX3ByaWNlcyA8LSBob3VzaW5nX3ByaWNlcyAlPiUgCiAgc2VsZWN0KC10b3RhbF9iZWRyb29tcykKYGBgCgp0b3RhbF9iZWRyb29tcyB3YXMgdGhlIG9ubHkgdmFyIHdpdGggbWlzc2luZyB2YWx1ZXMsIHNvIHdlIGFsc28gZG9uJ3QgbmVlZCB0byB3b3JyeSBhYm91dCB0aGVzZSBhbnltb3JlLgoKIyA0IGV4cGxvcmUgbnVtZXJpYyBwcmVkaWN0b3JzCgo+IFdlIGFyZSBpbnRlcmVzdGVkIGluIGRldmVsb3BpbmcgYSByZWdyZXNzaW9uIG1vZGVsIGZvciB0aGUgbWVkaWFuX2hvdXNlX3ZhbHVlIG9mIGEgaG91c2UgaW4gdGVybXMgb2YgdGhlIHBvc3NpYmxlIHByZWRpY3RvciB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQuCmkuIFVzZSBnZ3BhaXJzKCkgdG8gaW52ZXN0aWdhdGUgY29ycmVsYXRpb25zIGJldHdlZW4gbWVkaWFuX2hvdXNlX3ZhbHVlIGFuZCB0aGUgcHJlZGljdG9ycyAodGhpcyBtYXkgdGFrZSBhIHdoaWxlIHRvIHJ1biwgZG9u4oCZdCB3b3JyeSwgbWFrZSBjb2ZmZWUgb3Igc29tZXRoaW5nKS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KR0dhbGx5OjpnZ3BhaXJzKGhvdXNpbmdfcHJpY2VzKQpgYGAKCkZyb20gdGhlc2UsIGl0IGxvb2tzIGxpa2UgdGhlcmUgYXJlIG90aGVyZXhwbGFuYXRvcnkgdmFyaWFibGVzIHRoYXQgYXJlIHN0cm9uZ2x5IHJlbGF0ZWQ6CgoqIHBvcHVsYXRpb24sIGhvdXNlaG9sZCBhbmQgdG90YWxfcm9vbXMgYXJlIGFsbCBjb3JyZWxhdGVkIC0gdGhleSBjb3VsZCBiZSBjb21iaW5lZCB0byBjcmVhdGUgYSBkZXJpdmVkIHZhcmlhYmxlIGFzIHNvbWUga2luZCBvZiBpbmRpY2F0b3Igb2YgaG91c2luZyBzdXBwbHkvZGVtYW5kLCBlLmcuIGhvdXNlaG9sZHMgKiB0b3RhbF9yb29tcyAvIHBvcHVsYXRpb24gKHdoZXJlID4gMSBpcyBwbGVudHkgb2Ygc3VwcGx5LCA8IDEgaXMgbm90IGVub3VnaCBzdXBwbHksIHNvIHNvbWUgZGVtYW5kIHByZXNzdXJlKQoqIGxvbmcgYW5kIGxhdCBhcmUgdHdvIHBhcnRzIHRvIGEgbG9jYXRpb24sIGFuZCBhcmUgc3Ryb25nbHkgcmVsYXRlZCAtIHRoZXkgc2hvdWxkIGJlIGNvbWJpbmVkIHRvIGluZGljYXRlIGluZGl2aWR1YWwgbG9jYXRpb25zIChvciBhcmVhcykgaW4gQ2FsaWZvcm5pYSAod2hlcmUgdGhpcyBkYXRhIGlzIGFib3V0KQoKVGFraW5nIHRoZXNlIGludG8gYWNjb3VudCwgYW5kIGxvb2tpbmcgYXQgdGhlIG1hZ25pdHVkZSBvZiB0aGUgY29ycmVsYXRpb24gY29lZmZpY2llbnRzIHRoYXQgYXJlIHNpZ25pZmljYW50ICgqcyksIEkgd291bGQgY29uc2lkZXIgaW5jbHVkaW5nIHRoZSBmb2xsb3dpbmcgaW4gYSBtb2RlbCwgaW4gb3JkZXIgb2Ygc3RlcHM6CgoxLiBpbmNvbWUKMi4gbG9jYXRpb24gKGxhdCwgbG9uZykgLSBsb29rIGF0IGxhdCBiZWxvdwozLiBkZW1hbmQgKHNvbWV0aGluZyBkZXJpdmVkIGZyb20gcG9wLCBob3VzZWhvbGQsIHJvb21zKSAtIGxvb2sgYXQgcm9vbXMgYmVsb3cKICAtIG5vdGU6IHJvb21zIGlzIGFsc28gYW4gaW5kaWNhdG9yIG9mIGhvaXVzZSBzaXplLCBjb3VsZCBiZSB1c2VmdWwgb24gaXRzIG93bgo0LiBhZ2UKCj4gaWkuIFBlcmZvcm0gZnVydGhlciBnZ3Bsb3QgdmlzdWFsaXNhdGlvbnMgb2YgYW55IHNpZ25pZmljYW50IGNvcnJlbGF0aW9ucyB5b3UgZmluZC4KCmBgYHtyfQojIGluY29tZQpob3VzaW5nX3ByaWNlcyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbWVkaWFuX2luY29tZSwgeSA9IG1lZGlhbl9ob3VzZV92YWx1ZSkpICsKICBnZW9tX3BvaW50KGZpbGwgPSAiZ3JleTYwIiwgc2l6ZSA9IDAuNSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtKQpgYGAKCmBgYHtyfQpjb3IoaG91c2luZ19wcmljZXMkbWVkaWFuX2hvdXNlX3ZhbHVlLCBob3VzaW5nX3ByaWNlcyRtZWRpYW5faW5jb21lKQpgYGAKCioqSW50ZXJwcmV0OioqIFRoZXJlIGlzIGEgc3Ryb25nIHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gbWVkaWFuIGhvdXNlIHZhbHVlIGFuZCBtZWRpYW4gaW5jb21lIG9mIGFuIGFyZWEuIDY0JSBvZiB2YXJpYXRpb24gaW4gaG91c2UgdmFsdWUgaXMgZXhwbGFpbmVkIGJ5IHZhcmlhdGlvbiBpbiBpbmNvbWUuCgpUaGlzIGNvdWxkIGJlIGEgdXNlZnVsIGV4cGxhbmF0b3J5IHZhcmlhYmxlLgoKTm90ZSBhbHNvIHRoZXJlIHNlZW1zIHRvIGJlIGFuIHVwcGVyIGxpbWl0IGluIG1lZGlhbl9ob3VzZV92YWx1ZSwgb2RkIGxhY2sgb2YgdmFsdWVzIGFib3ZlIDUwMCwwMDAuCgoKYGBge3J9CiMgbGF0aXR1ZGUKaG91c2luZ19wcmljZXMgJT4lIAogIGdncGxvdChhZXMoeCA9IGxhdGl0dWRlLCB5ID0gbWVkaWFuX2hvdXNlX3ZhbHVlKSkgKwogIGdlb21fcG9pbnQoZmlsbCA9ICJncmV5NjAiKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG0pCmBgYAoKVGhpcyBpcyBub3QgYSBsaW5lYXIgcmVsYXRpb25zaGlwIC0gdGhlciBhcHBlYXIgdG8gYmUgY2x1c3RlcnMgd2hlcmUgaG91c2UgcHJpY2UgaXMgbW9yZSB2YXJpYWJsZSAoaGlnaGVyIHBlYWtzKSAtIGkuZS4gY2VydGFpbiBsYXRpdHVkZXMsIEkgc3VzcGVjdCB0aGVzZSBhcmUgd2hlcmUgY2l0aWVzIGFyZS4KCkRvIG5vdCB1c2UgdGhpcyBhcyBhIHZhcmlhYmxlIG9uIGl0cyBvd24gKGNvbnNpZGVyIHVzaW5nIGxvY2F0aW9uIHRob3VnaCAtIG1heWJlIGV2ZW4gZW5jb2RpbmcgYXMgZGlmZmVyZW50IGFyZWFzPykKCmBgYHtyfQojIHRvdGFsX3Jvb21zCmhvdXNpbmdfcHJpY2VzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB0b3RhbF9yb29tcywgeSA9IG1lZGlhbl9ob3VzZV92YWx1ZSkpICsKICBnZW9tX3BvaW50KGZpbGwgPSAiZ3JleTYwIikgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtKQpgYGAKClRoaXMgZG9lcyBub3QgbG9vayBob21vZXNrZWRhc3RpYywgbG90cyBvZiBkYXRhIHBvaW50cyBhdCBsb3dlciB0b3RhbF9yb29tcywgYnV0IHdpdGggaGlnaCBkZWdyZWUgb2Ygc3ByZWFkIG9mIGhvdXNlIHZhbHVlcy4gU3VnZ2VzdCB0b3RhbF9yb29tcyBtaWdodCBuZWVkIHRvIGJlIHN0YW5kYXJkaXNlZCBieSBzb21lIGZhY3Rvcj8KCmBgYHtyfQojIG1lZGlhbl9hZ2UKaG91c2luZ19wcmljZXMgJT4lIAogIGdncGxvdChhZXMoeCA9IGhvdXNpbmdfbWVkaWFuX2FnZSwgeSA9IG1lZGlhbl9ob3VzZV92YWx1ZSkpICsKICBnZW9tX3BvaW50KGZpbGwgPSAiZ3JleTYwIikgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtKQpgYGAKCk5vIG9idmlvdXMgY29ycmVsYXRpb24gaGVyZS4gRG8gbm90IHVzZSBpbiBtb2RlbC4KCiMgNSBleHBsb3JlIGNhdGVnb3JpY2FsIHByZWRpY3RvcgoKPiBTaG9ydGx5IHdlIG1heSB0cnkgYSByZWdyZXNzaW9uIG1vZGVsIHRvIGZpdCB0aGUgY2F0ZWdvcmljYWwgcHJlZGljdG9yIG9jZWFuX3Byb3hpbWl0eS4gSW52ZXN0aWdhdGUgdGhlIGxldmVsIG9mIG9jZWFuX3Byb3hpbWl0eSBwcmVkaWN0b3JzLiBIb3cgbWFueSBkdW1teSB2YXJpYWJsZXMgZG8geW91IGV4cGVjdCB0byBnZXQgZnJvbSBpdD8KCmBgYHtyfQpob3VzaW5nX3ByaWNlcyAlPiUgCiAgZGlzdGluY3Qob2NlYW5fcHJveGltaXR5KQpgYGAKClRoZXJlIGFyZSA1IGRpc3RpbmN0IGNhdGVnb3JpZXMgaW4gb2NlYW5fcHJveGltaXR5LCBzbyBleHBlY3QgdG8gZ2VuZXJhdGUgNCBkdW1teSB2YXJpYWJsZXMuICoqU2V0IGlubGFuZCBhcyB0aGUgcmVmZXJlbmNlKiosIGV2ZXJ5dGhpbmcgZWxzZSBnZXRzIG1vcmUgcHJveGltYWwgdG8gb2NlYW4uCgpgYGB7cn0KaG91c2luZ19wcmljZXNfdHJpbSA8LSBob3VzaW5nX3ByaWNlcyAlPiUgCiAgIyBzZXQgdXAgZHVtbXkgdmFyaWFibGVzIGZvciBvY2Vhbl9wcm94aW1pdHkKICBmYXN0RHVtbWllczo6ZHVtbXlfY29scyhzZWxlY3RfY29sdW1ucyA9ICJvY2Vhbl9wcm94aW1pdHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZV9zZWxlY3RlZF9jb2x1bW5zID0gVFJVRSkgJT4lIAogIGphbml0b3I6OmNsZWFuX25hbWVzKCkgJT4lIAogICMgc2V0IGlubGFuZCBhcyByZWZlcmVuY2UKICBzZWxlY3QoLW9jZWFuX3Byb3hpbWl0eV9pbmxhbmQpCmBgYAoKYGBge3J9CiMgY2hlY2sgZm9yIGFueSBwb3RlbnRpYWwgY29ycmVsYXRpb25zCmhvdXNpbmdfcHJpY2VzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBvY2Vhbl9wcm94aW1pdHksIHkgPSBtZWRpYW5faG91c2VfdmFsdWUpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgpIb3VzZSBwcmljZXMgb24gaXNsYW5kIGxvb2sgaGlnaGVyIHRoYW4gdGhlIHJlc3QsIHRoaXMgbWF5IGJlIGEgc2Vuc2libGUgcHJlZGljdG9yIHRvIHVzZSBpbiBhIG1vZGVsLgoKCioqVXBkYXRlOioqIGZyb20gYm94cGxvdCwgYWN0dWFsbHkgbG9va3MgbGlrZSAzIGNhdGVnb3JpZXMgdGhhdCBhcmUgc2ltaWxhcjogaW5sYW5kLCBpc2xhbmQsIGFuZCBwcm94aW1hbCB0byBvY2VhbiAoY29tYmluaW5nIHRoZSAzIG90aGVycykgLS0+IHdoaWNoIHdvdWxkIHN1Z2dlc3QgMiBkdW1teSB2YXJpYWJsZXMgKHdpdGggaW5sYW5kIHNldCBhcyByZWZlcmVuY2UpLgoKYGBge3J9CmhvdXNpbmdfcHJpY2VzICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCksIC5ieSA9IG9jZWFuX3Byb3hpbWl0eSkKYGBgCgp+MjAsMDAwIG9ic2VydmF0aW9ucyBvZiB3aGljaCwgOCw2MDAgYXJlIDwxaCB0byBvY2VhbiwgNSBhcmUgb24gaXNsYW5kLCAyMDAwIGFyZSBuZWFyIGJheSwgMjQ0NiBhcmUgbmVhciBvY2VhbiwgcmVzdCBhcmUgaW5sYW5kICh+Niw1MDApLgoKVGhlcmUgYXJlIG9ubHkgNSBvYnNlcnZhdGlvbnMgZm9yIGlzbGFuZCwgc28gbm90IGVub3VnaCBkYXRhIGhlcmUgdG8gbW9kZWwgdGhpcyBjYXRlZ29yeSwgYW5kIGFsc28gZHJhZ2dpbmcgdXAgaG91c2UgcHJpY2VzIC0gc3VnZ2VzdCBkcm9wcGluZyB0aGlzIGNhdGVnb3J5IGFsdG9nZXRoZXIgZm9yIG5vdy4KClNvIGxldCdzIHVzZSBpbmxhbmQgYXMgcmVmZXJlbmNlLCBkcm9wIGlzbGFuZCBvYnNlcnZhdGlvbnMsIGFuZCBjb21iaW5lIHRoZSByZXN0IGludG8gYSBwcmVkaWN0b3IgIm9jZWFuX3Byb3hpbWFsIjoKCmBgYHtyfQpob3VzaW5nX3ByaWNlc19vY2VhbiA8LSBob3VzaW5nX3ByaWNlcyAlPiUgCiAgZmlsdGVyKG9jZWFuX3Byb3hpbWl0eSAhPSAiSVNMQU5EIikgJT4lIAogIG11dGF0ZShvY2Vhbl9wcm94aW1hbCA9IGlmX2Vsc2UoCiAgICBvY2Vhbl9wcm94aW1pdHkgPT0gIklOTEFORCIsCiAgICBGQUxTRSwgVFJVRSkpICU+JSAKICBzZWxlY3QoLW9jZWFuX3Byb3hpbWl0eSkKCmhlYWQoaG91c2luZ19wcmljZXNfb2NlYW4pCmBgYAoKIyMjIGhvbWV3b3JrIHJldmlldwoKSW4gUiwgd2UgZG9uJ3QgbmVlZCB0byBtYW51YWxseSBtYWtlIHRoZSBkdW1teSB2YXJpYWJsZXMgLSBqdXN0IGtlZXAgdGhlIG9jZWFuX3Byb3hpbWl0eSBjb2x1bW4gYW5kIHBhc3MgdGhpcyBpbnRvIHRoZSBtb2RlbCBiZWxvdy4gU2ltaWxhciByZXN1bHRzLCBzZWUgdGhlIGNvZWZmaWNpZW50IGVzdGltYXRlcyBmb3IgZWFjaCB0eXBlIG9mIG9jZWFuX3Byb3hpbWl0eS4KCkV4YW1wbGU6IAoKYGBge3J9CiMgaXJpcyBkYXRhc2V0LCAxIGNvbnRpbnVvdXMsIDEgY2F0ZWdvcmljYWwgdmFyCmxtMSA8LSBsbShQZXRhbC5MZW5ndGggfiBQZXRhbC5XaWR0aCArIFNwZWNpZXMsIGlyaXMpCgpoZWFkKG1vZGVsLm1hdHJpeChsbTEpKQojIHNob3dzIHRoYXQgdGhlIFNwZWNpZXMgdmFyIGFyZSBkdW1teSBlbmNvZGluZyBhdXRvbWF0aWNhbGx5ICgzIHNwZWNpZXMsIG1pc3NpbmcgdGhlIGZpcnN0IHNwZWNpZXMgaW4gYWxwaGFiZXRpY2FsIG9yZGVyKToKYGBgCgpBbHNvOiBpdCdzIG9rIHRvIGtlZXAgdGhlIGlzbGFuZCBpbiB3aXRoIDUgb2JzZXJ2YXRpb25zOyB0aGUgZGlmZmVyZW5jZSBpbiBzYW1wbGUgc2l6ZSBpcyBub3QgYSBwcm9ibGVtLiBJdCBtaWdodCBtZWFuIHdlIGFyZSBsZXNzIGVtcG93ZXJlZCB0byBtYWtlIHByZWRpY3Rpb25zIGFib3V0IGlzbGFuZCBob3VzZSB2YWx1ZXMgbGF0ZXIsIGJ1dCBpdCdzIG9rIHRvIGluY2x1ZGUgaW4gdGhlIG1vZGVsLiBBbHNvIG9rIHRvIGNvbWJpbmUgc2ltaWxhciBncm91cHMuCgpJZiB5b3UgaW5jbHVkZSBuIGR1bW15IHZhcmlhYmxlcywgeW91J2xsIGdldCBvbmUgb2YgdGhlbSB3aXRoIGEgbGluZSBvZiBOQXMgaW4gc3VtbWFyeShtb2RlbCkgPC0gYSBnb29kIHNpZ24gb2YgbXVsdGljb2xpbmVhcml0eSBhbmQgeW91IG5lZWQgdG8gcmVtb3ZlIDEgZHVtbXkgdmFyLgoKIyA2IHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiB3aXRoIGluY29tZQoKPiBTdGFydCB3aXRoIHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbi4gUmVncmVzcyBtZWRpYW5faG91c2VfdmFsdWUgb24gbWVkaWFuX2luY29tZSBhbmQgY2hlY2sgdGhlIHJlZ3Jlc3Npb24gZGlhZ25vc3RpY3MuCgpgYGB7cn0KbW9kZWxfaW5jb21lIDwtIGxtKGZvcm11bGEgPSBtZWRpYW5faG91c2VfdmFsdWUgfiBtZWRpYW5faW5jb21lLCBob3VzaW5nX3ByaWNlc190cmltKQpgYGAKCmBgYHtyfQojIGFzIGFib3ZlCm1vc2FpYzo6cGxvdE1vZGVsKG1vZGVsX2luY29tZSkKYGBgCgpgYGB7cn0KbGlicmFyeShnZ2ZvcnRpZnkpCmF1dG9wbG90KG1vZGVsX2luY29tZSkKYGBgCgpJbnRyZXByZXQ6CgoqIFJ2RiBwbG90IGhhcyBhIGJpZyB3ZWRnZSBzaGFwZSwgd2hpY2ggc3VnZ2VzdHMgaXQgaXMgaGV0ZXJza2VkYXN0aWMgLSB3ZSBuZWVkIHRvIGFkZCBhbm90aGVyIHZhcmlhYmxlIHRvIGV4cGxhaW4gdGhlIHZhcmlhdGlvbiBtb3JlIGNvbnNpc3RlbnRseSBhY3Jvc3MgeCB2YWx1ZXMgYW5kIGdlbmVyYXRlIGEgZ29vZCBsaW5lYXIgbW9kZWwgaGVyZQoqIFFRIHBsb3QgaXMgbm90IGxpbmVhciwgdGhlIHJlc2lkdWFscyBhcmUgbm90IG5vcm1hbGx5IGRpc3RydWJ0ZWQsIHdvcnNlIGF0IHRoZSBleHRyZW1lcywgbG9vayBuZWdhdGl2ZWx5IHNrZXdlZCAocmlnaHQgc2tld2VkKSBpbiB0aGUgaGlzdG9ncmFtIGJlbG93CiogU2NhbGUtTG9jYXRpb24gLSBsb29rcyB0byBiZSBzb21lIHBvdGVudGlhbCBvdXRsaWVycywgcG9pbnRzIDExMzMxIGFuZCAxNzU1Lj8uIChjYW5ub3QgZXhwYW5kIHBsb3QgdG8gc2VlKQoqIFRoZXNlIHBvaW50cyBhbmQgYW5vdGhlciBwb2ludCAoMTU2NS4uPykgaGF2ZSBoaWdoIGxldmVyYWdlIGFuZCBsb29rIHRvIGJlIGluZmx1ZW5jaW5nIGluIHRoZSBSdkwgcGxvdAoKYGBge3J9CiMgaW5zcGVjdCBkaXN0cmlidXRpb24gb2YgdGhlIHJlc2lkdWFscwpoaXN0KG1vZGVsX2luY29tZSRyZXNpZHVhbHMpCmBgYAoKYGBge3J9CiMgaW5zcGVjdCB0aGUgaGlnaCBsZXZlcmFnZSAvIGluZmx1ZW5jaW5nIHBvaW50cwpob3VzaW5nX3ByaWNlc190cmltICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBtZWRpYW5faW5jb21lLCB5ID0gbWVkaWFuX2hvdXNlX3ZhbHVlKSkgKwogIGdlb21fcG9pbnQoZmlsbCA9ICJncmV5NjAiLCBzaXplID0gMC41KSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG0pICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gMTpucm93KGhvdXNpbmdfcHJpY2VzKSksIG51ZGdlX3ggPSAwLjUsIG51ZGdlX3kgPSAxLCBzaXplID0gMikKYGBgCgpQb2ludHMgMTEzMzEsIDE3NTU2IGFuZCAxNTU1IGFwcGVhciB0byBiZSB0aGUgcG9pbnRzIG9mIGludGVyZXN0LgoKYGBge3J9CmhvdXNpbmdfcHJpY2VzW2MoMTEzMzEsMTc1NTYsMTU1NSksXQpgYGAKClJhdyBkYXRhIGRvZXNuJ3QgbG9vayBzdXNwZWN0LCBub3QgYW4gb2J2aW91cyBwcm9jZXNzaW5nIGVycm9yLiBCdXQgbm90ZSB0aGUgcG9wdWxhdGlvbiBsb29rcyB0aW55LCB2ZXJ5IGZldyBob3VzZWhvbGRzLiBNaWdodCB3YW50IHRvIGNvbnNpZGVyIGZpbHRlcmluZyBmb3IgZGF0YSB3aGVyZSBob3VzZWhvbGRzIGlzIHNpemVhYmxlIGVub3VnaC4uLgoKYGBge3J9CmhvdXNpbmdfcHJpY2VzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBwb3B1bGF0aW9uKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAsIGNvbG91ciA9ICJ3aGl0ZSIpCgpob3VzaW5nX3ByaWNlcyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gaG91c2Vob2xkcykpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwLCBjb2xvdXIgPSAid2hpdGUiKQpgYGAKCkl0IGRvZXNuJ3QgbWFrZSBzZW5zZSB0byBmaWx0ZXIgb3V0IGxvdyBudW1iZXIgaG91c2Vob2xkcyBvciBwb3B1bGF0aW9uLCBnaXZlbiB0aGUgZGlzdHJpYnV0aW9ucyBoZXJlIC0gdGhleSBkb24ndCBzZWVtIHRvIGJlIG9kZC4KClRyeSByZW1vdmluZyBhIGNvdXBsZSBvZiBwb2ludHM6CgpgYGB7cn0KIyByZW1vdmUgdGhlIHR3byBtb3N0IGNvbmNlcm5pbmcgcG9pbnRzCmhvdXNpbmdfcHJpY2VzX3JtIDwtIGhvdXNpbmdfcHJpY2VzX3RyaW0gJT4lIAogIHNsaWNlKC1jKDExMzMxLDE3NTU2KSkKCm1vZGVsX2luY29tZV9ybSA8LSBsbShtZWRpYW5faG91c2VfdmFsdWUgfiBtZWRpYW5faW5jb21lLCBob3VzaW5nX3ByaWNlc19ybSkKCmF1dG9wbG90KG1vZGVsX2luY29tZV9ybSkKYGBgCgpSZW1vdmluZyB0aGVzZSB2YWx1ZXMgaGFzIG5vdCBoZWxwZWQgdGhlIG1vZGVsLCBpdCBqdXN0IHNob3dzIGV2ZW4gbW9yZSBleHRyZW1lIHZhbHVlcyAoaGlnaCBpbmNvbWUsIGxvdyBob3VzZSBwcmljZSkuIFNvIGRvIG5vdCByZW1vdmUgdGhlc2UgcG9pbnRzIChjb250aW51ZSB3aXRoIGBob3VzaW5nX3ByaWNlc190cmltYCkuCgpgYGB7cn0Kc3VtbWFyeShtb2RlbF9pbmNvbWUpCmBgYAoKSGVyZSwgdGhlIG1vZGVsIHN1Z2dlc3RzIGEgZnVuY3Rpb246CgptZWRpYW5faG91c2VfdmFsdWUgKGluICQpIH49IH40MCwwMDAqbWVkaWFuX2luY29tZSArIDQ1LDUwMAoKdGhlIHAtdmFsdWUgKDwyLjJlLTE2KSBpcyB2ZXJ5IHNtYWxsIChtdWNoIGJlbG93IGEgMC4wNSBzaWduaWZpY2FuY2UgdGhyZXNob2xkKSwgd2hpY2ggaW5kaWNhdGVzIGl0IGlzIGhpZ2hseSB1bmxpa2VseSB0aGlzIG5vbi16ZXJvIGNvZWZmaWNpZW50IHdvdWxkIGhhcHBlbiBpbiBhIHdvcmxkIHdoZXJlIHRoZSBudWxsIGh5cG90aGVzaXMgaXMgdHJ1ZSAodGhhdCBtZWRpYW5faW5jb21lIGhhcyBubyBhZmZlY3Qgb24gbWVkaWFuX2hvdXNlX3ZhbHVlKSwgc28gd2UgY2FuIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzIGFuZCBzYXkgdGhlcmUgaXMgZW5vdWdoIGV2aWRlbmNlIHRvIHN1Z2dlc3QgdGhhdCBpbmNvbWUgZG9lcyBhZmZlY3QgaG91c2UgdmFsdWUuCgpUaGUgc3RhbmRhcmQgZXJyb3IgaXMgc2l6ZWFibGUgKH5VU0QgNzUsMDAwKSAtIHdlIGFyZSB0YWxraW5nIGFib3V0IGhvdXNlIHByaWNlcyBpbiB0aGUgcmVnaW9uIG9mIFVTRCAzMDAtNTAwLDAwMCB3aXRoIGEgYW4gSVFSIG9mIH4gMTMwICh0aGUgUVEgcmFuZ2UgaXMgfiAxMjAtMjUwayBpbiB0aGUgYm94cGxvdCBiZWxvdykgc28gdGhpcyBlcnJvciBpcyBoYWxmIHRoZSBJUVIgc28gaXMgcXVpdGUgbGFyZ2UgZm9yIHRoaXMgdG8gYmUgYSBnb29kIG1vZGVsLgoKYGBge3J9CmhvdXNpbmdfcHJpY2VzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBtZWRpYW5faG91c2VfdmFsdWUpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgpUaGUgUjIgaXMgMC40MTI5LCB3aGljaCBpcyBhbiBvayBjb3JyZWxhdGlvbiBmb3Igc3VjaCBhIHZhcmlhYmxlIG91dGNvbWUgKGhvdXNlIHZhbHVlKS4KCioqT3ZlcmFsbCoqIHRoaXMgc2ltcGxlIGxpbmVhciBtb2RlbCBkb2VzIG5vdCBtZWV0IHNldmVyYWwgYXNzdW1wdGlvbnMgb2YgbGluZWFyIHJlZ3Jlc3Npb24sIHNvIHdlIHNob3VsZCBub3QgYWNjZXB0IHRoYXQgdGhpcyBtb2RlbCBpcyBhIGdvb2QgZml0IGZvciBvdXIgZGF0YS4gV2UgbmVlZCB0byBpbXByb3ZlIGl0IC0gd2UgY2FuIHRyeSBhZGRpbmcgYW5vdGhlciBwcmVkaWN0b3IgdmFyaWFibGUgaW4uLi4KCiMgNyBhZGQgYW5vdGhlciB2YXIgdG8gbW9kZWwKCj4gQWRkIGFub3RoZXIgcHJlZGljdG9yIG9mIHlvdXIgY2hvaWNlLiBDaGVjayB5b3VyIGFzc3VtcHRpb25zLCBkaWFnbm9zdGljcywgYW5kIGludGVycHJldCB0aGUgbW9kZWwuCgojIyA3YSArIGlzbGFuZAoKTGV0J3MgdHJ5IHdpdGggb2NlYW4gcHJveGltaXR5OiBpc2xhbmQuIAoKQ29uc2lkZXJpbmcgaXQgYXMgYW4gaW5kZXBlbmRlbnQgdmFyaWFibGUsIGJ1dCBub3QgdXNpbmcgdGhlIG90aGVyIG9jZWFuX3Byb3hpbWl0aWVzIC0tIG5vdCBzdXJlIGlmIHRoaXMgaXMgYSBjb3JyZWN0IGFwcHJvYWNoIGZvciBub24tYmluYXJ5IGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4uLgoKYGBge3J9Cm1vZGVsX2luY29tZV9pc2xhbmQgPC0gbG0obWVkaWFuX2hvdXNlX3ZhbHVlIH4gbWVkaWFuX2luY29tZSArIG9jZWFuX3Byb3hpbWl0eV9pc2xhbmQsIGhvdXNpbmdfcHJpY2VzX3RyaW0pCmBgYAoKYGBge3J9CmF1dG9wbG90KG1vZGVsX2luY29tZV9pc2xhbmQpCmBgYAoKT2ggdGhpcyBoYXMgbm90IGltcHJvdmVkIHRoZSBtb2RlbC4uLiAKCiogUnZGIC0gc3RpbGwgZ290IGEgd2VkZ2Ugc2hhcGUsIHNvIG5vdCBsaW5lYXIKKiBRUSAtIHJlc2lkdWFscyBzdGlsbCBub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQKKiBTLUwgc3RpbGwgZ290IGEgd2VkZ2UgaGVyZSB0b28sIHNvIGhldGVyc2tlZGFzdGljCiogUnZMIC0gc2hvd3MgbWFueSBwb2ludHMgYXJlIG5lYXIgdGhlIGNlbnRyb2lkLCBhIGNvdXBsZSBvZiBwb2ludHMgYXJlIHZlcnkgZmFyCgpgYGB7cn0KaGlzdChtb2RlbF9pbmNvbWVfaXNsYW5kJHJlc2lkdWFscykKYGBgCgpUaGlzIGRpc3RyaWJ1dGlvbiBkb2Vzbid0IGxvb2sgc28gYmFkIHRob3VnaCAtIG1heWJlIGl0IGlzIHNoaWZ0ZWQgbGVmdD8KCmBgYHtyfQpzdW1tYXJ5KG1vZGVsX2luY29tZV9pc2xhbmQpCmBgYAoKYGBge3J9Cm1vc2FpYzo6cGxvdE1vZGVsKG1vZGVsX2luY29tZV9pc2xhbmQpCmBgYAoKSSB3b3VsZCBleHBlY3QgdG8gaGF2ZSBzZWVuIHBhcmFsbGVsIHNsb3BlcyBoZXJlIChvbmUgZm9yIGlzbGFuZCA9IFRSVUUsIGFub3RoZXIgZm9yIGlzbGFuZCA9IEZBTFNFKSAtIGJ1dCB0aGVyZSBpcyBvbmx5IG9uZSBsaW5lLgoKYGBge3J9CmhvdXNpbmdfcHJpY2VzX3RyaW0gJT4lIAogIGZpbHRlcihvY2Vhbl9wcm94aW1pdHlfaXNsYW5kID09IDEpCmBgYApUaGVyZSBhcmUgb25seSA1IG9ic2VydmF0aW9ucyB3aXRoIGlzbGFuZCA9PSBUUlVFIHNvIGNhbm5vdCB1c2UgdGhpcyBpbiBvdXIgbW9kZWwhCgpUcnkgYWdhaW4uLi4KCiMjIDdiICsgaG91c2Vfc2l6ZSAvIG9jY3VwYW5jeQoKTGV0J3MgZGVyaXZlIG1lYW4gaG91c2Vfc2l6ZSB1c2luZyB0b3RhbF9yb29tcy9ob3VzZWhvbGRzCgpgYGB7cn0KaG91c2luZ19wcmljZXNfdHJpbSA8LSBob3VzaW5nX3ByaWNlc190cmltICU+JSAKICBtdXRhdGUobWVhbl9ob3VzZV9zaXplID0gdG90YWxfcm9vbXMgLyBob3VzZWhvbGRzKQoKaGVhZChob3VzaW5nX3ByaWNlc190cmltKQpgYGAKCmBgYHtyfQpob3VzaW5nX3ByaWNlc190cmltICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBtZWFuX2hvdXNlX3NpemUsIHkgPSBtZWRpYW5faG91c2VfdmFsdWUpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC41KQpgYGAKClRoaXMgaXMgYSBuby1nb2VyIC0gbG9va3MgbGlrZSBtYWpvcml0eSBvZiBob3VzZXMgYXJlIGFyb3VuZCB0aGUgc2FtZSBzaXplLCB3aXRoIGEgZmV3IGV4dHJlbWUgdmFsdWVzLi4uCgpXaGF0IGFib3V0IGRlbWFuZCwgc29tZSBraW5kIG9mIG1lYXN1cmUgb2YgaG91c2Vob2xkcyBieSBwb3B1bGF0aW9uPyBEZXJpdmUgYXZnX29jY3VwYW5jeQoKYGBge3J9CmhvdXNpbmdfcHJpY2VzX3RyaW0gPC0gaG91c2luZ19wcmljZXNfdHJpbSAlPiUgCiAgc2VsZWN0KC1tZWFuX2hvdXNlX3NpemUpICU+JSAKICBtdXRhdGUoYXZnX29jY3VwYW5jeSA9IHBvcHVsYXRpb24gLyBob3VzZWhvbGRzKSAjIGluZGljYXRlcyAjIHBlb3BsZSBwZXIgaG91c2Vob2xkCgpoZWFkKGhvdXNpbmdfcHJpY2VzX3RyaW0pCmBgYAoKYGBge3J9CmhvdXNpbmdfcHJpY2VzX3RyaW0gJT4lIAogIGdncGxvdChhZXMoeCA9IGF2Z19vY2N1cGFuY3ksIHkgPSBtZWRpYW5faG91c2VfdmFsdWUpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC41KQpgYGAKCk5vdyB0aGVyZSBhcmUgNyBwb2ludHMgdGhhdCBsb29rIGxpa2UgZXh0cmVtZSB2YWx1ZXMuLi4gY291bGQgd2UgZmlsdGVyIGZvciBhdmdfb2NjdXBhbmN5IDwgMTAwPz8KClRoaXMgbWF5IGJlIG92ZXItd3JhbmdsaW5nIHRoZSBkYXRhLCBidXQgdGhlc2UgZmV3IHZhbHVlcyBkbyBub3QgbWFrZSBzZW5zZSBmb3IgYSBtb2RlbCBsb29raW5nIGF0IH4yMCwwMDAgb2JzZXJ2YXRpb25zCgpgYGB7cn0KbnJvdyhob3VzaW5nX3ByaWNlc190cmltKQpgYGAKCmBgYHtyfQojID4gMTAwIGZpbHRlcnMgNCBvdXQKIyA+IDUwIGZpbHRlcnMgNyBvdXQKaG91c2luZ19wcmljZXNfcm1faGlnaF9vY2N1cGFuY3kgPC0gaG91c2luZ19wcmljZXNfdHJpbSAlPiUgCiAgZmlsdGVyKCFhdmdfb2NjdXBhbmN5ID4gNTApCmBgYAoKYGBge3J9CmhvdXNpbmdfcHJpY2VzX3JtX2hpZ2hfb2NjdXBhbmN5ICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhdmdfb2NjdXBhbmN5LCB5ID0gbWVkaWFuX2hvdXNlX3ZhbHVlKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSkKYGBgCgpOb3cgd2UgY2FuIHNlZSBzY2F0dGVyIG9mIGRhdGEsIGJ1dCBzdGlsbCBkb2VzIG5vdCBsb29rIHRvIGhhdmUgYW55IGxpbmVhciByZWxhdGlvbnNoaXAuIENvdWxkIHRyeSBzY2FsaW5nIGJ1dCBJIGRvbid0IHRoaW5rIHRoYXQgaXMgdGhlIHByb2JsZW0gaGVyZS4uLgoKIyMgN2MgKyBvY2Vhbl9wcm94aW1pdHkKClVzaW5nIGBob3VzZV9wcmljZXNfb2NlYW5gIGZvciBvdXIgZHVtbXkgdmFyaWFibGUgb2NlYW5fcHJveGltYWw6CgpgYGB7cn0KaG91c2luZ19wcmljZXNfb2NlYW4gJT4lIAogIGdncGxvdChhZXMoeCA9IG9jZWFuX3Byb3hpbWFsLCB5ID0gbWVkaWFuX2hvdXNlX3ZhbHVlKSkgKwogIGdlb21fYm94cGxvdCgpCmBgYAoKVGhlcmUgbG9va3MgdG8gYmUgYW4gZWZmZWN0IG9mIG9jZWFuIHByb3hpbWl0eSB3aXRoIGhvdXNlIHByaWNlcywgd2hlcmUgYmVpbmcgY2xvc2UgdG8gdGhlIG9jZWFuIGhhcyBoaWdoZXIgaG91c2UgcHJpY2VzLCBidXQgdGhlcmUncyBhbHNvIGEgbG9uZyB0YWlsIG9mIHZhbHVlcyBmb3IgRkFMU0UuIExldCdzIHRyeSBpbmNsdWRpbmcgdGhpcyBpbiBvdXIgbW9kZWwgYXMgYSBzZWNvbmQgZXhwbGFuYXRvcnkgdmFyaWFibGU6CgpgYGB7cn0KbW9kZWxfaW5jb21lX29jZWFuIDwtIGxtKG1lZGlhbl9ob3VzZV92YWx1ZSB+IG1lZGlhbl9pbmNvbWUgKyBvY2Vhbl9wcm94aW1hbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICBob3VzaW5nX3ByaWNlc19vY2VhbikKYGBgCgpgYGB7cn0KbW9zYWljOjpwbG90TW9kZWwobW9kZWxfaW5jb21lX29jZWFuKQpgYGAKClRoaXMgZ2l2ZXMgdXMgMiBwYXJhbGxlbCBsaW5lcyAtIGl0IGxvb2tzIGFzIHRob3VnaCBiZWluZyBjbG9zZSB0byB0aGUgb2NlYW4gc2hpZnRzIG1lZGlhbiBob3VzZV92YWx1ZSBoaWdoZXIuCgpMZXQncyBkaWFnbm9zZSB0aGlzIG1vZGVsOgoKYGBge3J9CmF1dG9wbG90KG1vZGVsX2luY29tZV9vY2VhbikKYGBgCgoxLiBSdkYgLSBzdGlsbCBhIHdlZGdlIHNoYXBlLCBzbyBub3QgbGluZWFyCjIuIFFRIHBsb3QgZG9lcyBub3Qgc3VnZ2VzdCBub3JtYWxseSBkaXN0cmlidXRlZCBhdCB0aGUgZXh0cmVtZXMKMy4gUy1MIC0gc3RpbGwgYSB3ZWRnZSBzaGFwZSBzbyBoZXRlcnNrZWRhc3RpYwo0LiB0aGVyZSBtYXkgc3RpbGwgYmUgYSBjb3VwbGUgb2YgcG9pbnRzIHRoYXQgYXJlIGluZmx1ZW5jaW5nIHRoZSBtb2RlbCBoZXJlCgpTbyB0aGUgbW9kZWwgZG9lcyBub3QgbWF0Y2ggb3VyIGFzc3VtcHRpb25zIG9mIGxpbmVhciByZWdyZXNzaW9uLgoKTGV0J3MgbG9vayBhdCB0aGUgbW9kZWwgYW55d2F5Li4uIAoKYGBge3J9CnN1bW1hcnkobW9kZWxfaW5jb21lX29jZWFuKQpgYGAKClRoaXMgbW9kZWwgKGlmIGl0IHdlcmUgYSBnb29kIGZpdCkgc3VnZ2VzdHMgYmFzZSBtZWRpYW4gaG91c2UgdmFsdWUgaXMgVVNEIDEyLDAwMCBhbmQgaW5jcmVhc2VzIHdpdGggbWVkaWFuIGluY29tZSAobXVsdGlwbGllciBlZmZlY3QsICozNSwwMDApLCBhbmQgaXMgYWxzbyBzaGlmdGVkIHVwIGlmIHRoZSBsb2NhdGlvbiBpcyBjbG9zZSB0byB0aGUgb2NlYW4gKGJ5IFVTRCA3OCwwMDApLgoKVGhlIFIyIGluZGljYXRlcyB0aGUgbW9kZWwgaXMgYSBnb29kIGZpdCwgYW5kIHRoZSBwLXZhbHVlIGlzIHdheSBiZWxvdyBhIHNpZ25maWNhbmNlIHRocmVzaG9sZCBvZiAwLjA1LCBzdWdnZXN0aW5nIHdlIGNhbiByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyAodGhhdCB0aGVzZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMgZG8gbm90IHByZWRpY3QgaG91c2UgdmFsdWUpLgoKVGhpcyBtb2RlbCBzdW1tYXJ5IHdvdWxkIGxvb2sgZ29vZCwgZXhjZXB0IHRoYXQgbG9va2luZyBhdCBhdXRvcGxvdCBpbmRpY2F0ZXMgdGhlIGxpbmVhciBtb2RlbCBpcyBub3QgYXBwcm9wcmlhdGUgaGVyZS4KClRvIHRyeTogc3RhbmRhcmRpc2Ugb3Igc2NhbGUgdGhlIHZhcmlhYmxlcyBzb21laG93PyBCdXQgdGhpcyB3b24ndCBjaGFuZ2UgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlYWNoIChpLmUuIHdvbid0IHNoaWZ0IHRoZSBsb25nIHRhaWwgb2YgaGlnaCBob3VzZSBwcmljZXMgb3IgaGlnaCBpbmNvbWUuLi4pCg==